package com.audaque.springboot.foshanupload.web.springsecuritycas.config;

import com.audaque.springboot.foshanupload.core.properties.StaticResourcePathProperties;
import com.audaque.springboot.foshanupload.web.springsecuritycas.component.MyFilterInvocationSecurityMetadataSource;
import com.audaque.springboot.foshanupload.web.springsecuritycas.filter.RequestUrlFilter;

import com.audaque.springboot.foshanupload.web.springsecuritycas.handler.MyAccessDeniedHandler;
import com.audaque.springboot.foshanupload.web.springsecuritycas.manager.MyAccessDecisionManager;
import org.jasig.cas.client.session.SingleSignOutFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.cas.authentication.CasAuthenticationProvider;
import org.springframework.security.cas.web.CasAuthenticationEntryPoint;
import org.springframework.security.cas.web.CasAuthenticationFilter;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.web.cors.CorsUtils;

import java.util.List;

@Configuration
@Order(SecurityProperties.BASIC_AUTH_ORDER)
public class CasWebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private CasAuthenticationEntryPoint casAuthenticationEntryPoint;


    @Autowired
    private CasAuthenticationFilter casAuthenticationFilter;

    @Autowired
    private LogoutFilter logoutFilter;

    @Autowired
    private StaticResourcePathProperties staticResourcePathProperties;

    @Autowired
    private RequestUrlFilter requestUrlFilter;

    @Autowired
    private MyFilterInvocationSecurityMetadataSource myFilterInvocationSecurityMetadataSource;

    @Autowired
    private MyAccessDecisionManager myAccessDecisionManager;

    @Autowired
    private CasAuthenticationProvider casAuthenticationProvider;

    @Autowired
    private CasServerConfig casServerConfig;

    @Autowired
    private CasServiceConfig casServiceConfig;

    /**
     * configure(WebSecurity web)不走过滤链的放行
     * 即不通过security 完全对外的/最大级别的放行
     **/

    /*用于影响全局安全性(配置资源，设置调试模式，通过实现自定义防火墙定义拒绝请求)的配置设置。
    一般用于配置全局的某些通用事物，例如静态资源等
    */
    // 直接放行了  如果是login接口 必须通过HttpSecurity 走过滤链 因为登录涉及 SecurityContextHolder
    @Override
    public void configure(WebSecurity web) throws Exception {
        List<String> list = staticResourcePathProperties.getList();
        String[] arr = list.toArray(new String[list.size()]);
        web.ignoring().antMatchers(
           arr
        );

        super.configure(web);
    }


    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        /*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/

        // 开启跨域
        httpSecurity
                .cors();
        /*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
        // 开启允许iframe 嵌套
        httpSecurity.headers().frameOptions().disable();
        /*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/

        httpSecurity
                // 禁用缓存
                .headers().cacheControl()
        ;
        /*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
        //匿名权限名称
        httpSecurity.anonymous()
                .authorities("ROLE_ANONYMOUS")
               ;
        /*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
        httpSecurity
                // 由于使用的是JWT，我们这里不需要csrf
                //配置跨站请求伪造保护
                .csrf()
                //cookie 可以限制::「使用方式」::。「配置：Secure / HttpOnly」
                //Secure
                //Secure属性指定浏览器只有在加密协议 HTTPS 下，才能将这个 Cookie 发送到服务器。另一方面，如果当前协议是 HTTP，浏览器会自动忽略服务器发来的Secure属性。该属性只是一个开关，不需要指定值。如果通信是 HTTPS 协议，该开关自动打开。
                //HttpOnly
                //HttpOnly属性指定该 Cookie 无法通过 JavaScript 脚本拿到，主要是Document.cookie属性、XMLHttpRequest对象和 Request API 都拿不到该属性。这样就防止了该 Cookie 被脚本读到，只有浏览器发出 HTTP 请求时，才会带上该 Cookie。
                //设置httponly=false，实现不限制cookie
                //.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
                //无cookie，关闭跨站请求伪造保护
                .disable()

        ;

        /*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/

        /**
         * s:授权配置:
         */
        //只在这里配置静态资源和跨域放行
        List<String> list = staticResourcePathProperties.getList();
        String[] arr = list.toArray(new String[list.size()]);
        httpSecurity.authorizeRequests()
                .antMatchers(HttpMethod.GET,  //允许对于网站静态资源的无授权访问
                        arr
        )
                .permitAll()

                .antMatchers(HttpMethod.OPTIONS)//跨域请求会先进行一次options请求
                .permitAll()


                .requestMatchers(CorsUtils::isPreFlightRequest)
                .permitAll()
                .antMatchers("/service/login")
                .anonymous()


                //.antMatchers("/").permitAll()
                //.antMatchers(HttpMethod.GET, "/login/**").permitAll()
        ;

        /**
         * e:授权配置
         */
        // 不拦截注销
        httpSecurity.logout().permitAll();

        /*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
        httpSecurity
                //注入异常处理过滤器
                .exceptionHandling()
                //鉴权失败处理器
                .accessDeniedHandler(new MyAccessDeniedHandler())
        //鉴证失败处理器
        .authenticationEntryPoint(casAuthenticationEntryPoint);



        // 单点注销的过滤器，必须配置在SpringSecurity的过滤器链中，如果直接配置在Web容器中，貌似是不起作用的。我自己的是不起作用的。
        SingleSignOutFilter singleSignOutFilter = new SingleSignOutFilter();
        singleSignOutFilter.setArtifactParameterName(this.casServerConfig.getHost());
        //注册过滤器到security过滤链
        httpSecurity.addFilter(casAuthenticationFilter)
                .addFilterBefore(logoutFilter, LogoutFilter.class)
                .addFilterBefore(singleSignOutFilter, CasAuthenticationFilter.class);
        httpSecurity.addFilterBefore(requestUrlFilter, FilterSecurityInterceptor.class);
        //FilterSecurityInterceptor是一个方法级的权限过滤器, 基本位于过滤链的最底部。
        //用于保护web资源的,使用AccessDecisionManager对当前用户进行授权访问
        //当到 FilterSecurityInterceptor 的时候会拿到 uri ，根据 uri 去找对应的鉴权管理器，鉴权管理器做鉴权工作，鉴权成功则到 Controller 层否则到 AccessDeniedHandler 鉴权失败处理器处理。
        FilterSecurityInterceptor filterSecurityInterceptor = new FilterSecurityInterceptor();
        //根据访问的路径判断出所需的角色
        filterSecurityInterceptor.setSecurityMetadataSource(myFilterInvocationSecurityMetadataSource);
        //实例化认证管理器
        filterSecurityInterceptor.setAuthenticationManager(authenticationManager());
        //鉴权处理器
        filterSecurityInterceptor.setAccessDecisionManager(myAccessDecisionManager);
        //注册过滤器到security拦截链
        httpSecurity.addFilterBefore(filterSecurityInterceptor, FilterSecurityInterceptor.class);

    }


    //作用: 用来将自定义AuthenticationManager在工厂中进行暴露,可以在任何位置注入
    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(casAuthenticationProvider);
    }


}
